package algebra.query.indexing;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

import option.InitParam;


import entities.assertion.ConceptAssertion;
import entities.concept.Concept;

import utils.Display;
import utils.exception.CoreReasoningException;
import utils.exception.IndexLoadingException;
import utils.exception.OrderingDescriptionException;
import utils.exception.SyntaxParsingException;
import utils.parser.Creator;
import utils.parser.Parser;

//import algebra.query.OrderingDescription;
import algebra.query.indexing.DescriptionIndex;
import algebra.query.indexing.IndexTreeNode;
import algebra.query.od.OrderDep;
import algebra.query.pd.ProjDesc;

import kb.DescriptionLogicsKB;

public abstract class CachedIndex {

	public abstract void buildIndex(String indexloc);

	public abstract DescriptionIndex getDescriptionIndex();

	public abstract String getName();

	public abstract String toSerializedString();

	public abstract Concept getCi();

	public abstract OrderDep getOdi();

	public abstract ProjDesc getPdi();

	public static final String indexDir = InitParam
			.getProperty("dir.indices.path");
	// build then save as file
	public static boolean buildSave = true;

	public final static String extensionIndex = InitParam
			.getProperty("queryindex.extension");
	public final static String serializedNameSep = InitParam
			.getProperty("queryindex.sep.name");

	public SecondaryIndex asSI() {
		return (SecondaryIndex) this;
	}

	public PrimaryIndex asPI() {
		return (PrimaryIndex) this;
	}

	/**
	 * Default file dir is the current working dir, recommended file name is
	 * generated by thisIndex.toString(); default encoding is "UTF8"
	 */
	public static void serializeCacheIndex(String src, DescriptionIndex idx,
			String encoding, String filename) {
		try {
			OutputStream os = new FileOutputStream(filename);
			if (encoding == null || encoding.trim().equals("")) {
				encoding = "UTF8";
			}
			idx.writeIndex(src, os, encoding);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
		}
	}

	// When kb is supplied as an arg, this method only loads indices that are
	// compatible with this KB.
	// When KB is null, no indices are loaded.
	public static Set<CachedIndex> readAllMaterilaizedIndices(
			DescriptionLogicsKB kb, String indexloc) 
					throws IndexLoadingException, OrderingDescriptionException, 
						CoreReasoningException {
		if (kb == null) {
			throw new IndexLoadingException(
					"You must supply a loaded KB for loading indices.");
		}
		String indexlocation = indexloc;
		if(indexloc == null || indexloc.trim().equals("")){
			//use teh default location
			indexlocation = indexDir;
		}
		Set<CachedIndex> sis = new HashSet<CachedIndex>();
		File folder = new File(indexlocation);
		File[] listOfFiles = folder.listFiles();
		for (File file : listOfFiles) {
			if (file.isFile()) {
				String fn = file.getName();
				if (fn.endsWith(extensionIndex)) {
					CachedIndex si = readSerializedCacheIndex(indexlocation + fn,
							null, kb);
					// have to create SIs to match them
					if (si == null) {
						continue;
					} else {
						sis.add(si);
						System.out.println("Index loaded: " + si.toString());
					}
				}
			}
		}
		// end
		return sis;
	}

	/**
	 * 
	 * @param filename
	 *            : the index file
	 * @param encoding
	 *            : the encoding used to read the above file
	 * @param kb
	 *            : the knowledge based that will be used in search/comparison; <br/>
	 *            if this kb is not the same one as that used in building this
	 *            very index file, the behavior is unpredictable.
	 * @return
	 * @throws CoreReasoningException 
	 * @throws OrderingDescriptionException 
	 */
	public static CachedIndex readSerializedCacheIndex(String filename,
			String encoding, DescriptionLogicsKB kb) throws OrderingDescriptionException, CoreReasoningException {
		InputStream is = null;
		try {
			is = new FileInputStream(filename);
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		}
		if (encoding == null || encoding.trim().equals("")) {
			encoding = "UTF8";
		}
		CachedIndex index = null;
		DescriptionIndex di = null;
		try {
			InputStreamReader isr = new InputStreamReader(is, encoding);
			BufferedReader in = new BufferedReader(isr);
			String line;
			while ((line = in.readLine()) != null) {
				if (line.startsWith(Display.COMMENTS)) {
					index = checksum(line, kb);
					if (index != null) {
						di = index.getDescriptionIndex();
						continue;
					} else {
						return null;
					}
				}
				try {
					if (di == null) {
						continue;
					}
					readIndexLine(line, di);// process di by each line
				} catch (SyntaxParsingException e) {
					e.printStackTrace();
				}
			}
			//balance
			di.postProcessing();
			//
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		return index;
	}

	// check if the DI read was produced by the same SI arguments!
	private static CachedIndex checksum(String line, DescriptionLogicsKB kb) {
		// name for SI: KB$C$Pd$Od.idx PI: KB$PrimaryIndex.idx
		String core = line.substring(line.lastIndexOf(Display.COMMENTS) + 1);
		core = core.trim();
		String[] es = core.split(Pattern.quote(serializedNameSep));
		if (es.length < 2 || es.length > 4) {
			System.err.println("Error in parsing the materialized query results: "
							+ line);
			return null;
		}
		OrderDep od = null;
		String kbname = es[0].trim();
		if (!kbname.equals(kb.getKBName())) {
			// System.err.println("The supplied index:"+line+" was not built from this KB:"+kb.getKBName());
			return null;
		}
		if (es.length == 4) {
			SecondaryIndex si = null;
			// Si name: kb$c$pd$od.idx
			try {
				Concept c = Parser.conParser(
						Parser.getListOfTokensFromTokenizer(es[1]), kb);
				ProjDesc pd = Creator
						.createProjDesc(Parser
								.getListOfTokensFromTokenizer(es[2]), kb);
				od = Creator.createOrderDep(Parser
						.getListOfTokensFromTokenizer(es[es.length - 1]));
				si = new SecondaryIndex(c, pd, od, kb);
				if (si != null) {
					DescriptionIndex di = new DescriptionIndex(od);
					si.setDI(di);
				}
				return si;
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else if (es.length == 2) {
			PrimaryIndex pi = new PrimaryIndex(kb);
			if (pi != null) {
				DescriptionIndex di = new DescriptionIndex(null);
				pi.setDescriptionIndex(di);
			}
			return pi;
			// PI,kb,pd=top?,od=un
			// od = null;
		}
		return null;
	}

	private static void readIndexLine(String line, DescriptionIndex di)
			throws SyntaxParsingException, OrderingDescriptionException, CoreReasoningException {
//		String[] ps = line.split(Pattern.quote(IndexTreeNode.pathsep));
//		if (ps.length == 1) {
//			// the root
//			ConceptAssertion ca = (ConceptAssertion) Parser.parseAssertions(
//					Parser.getListOfTokensFromTokenizer(ps[0]), null);
//			di.setRoot(new IndexTreeNode(ca));
//		} else if (ps.length == 2) {
//			// other nodes
//			ConceptAssertion ca = (ConceptAssertion) Parser.parseAssertions(
//					Parser.getListOfTokensFromTokenizer(ps[1]), null);
//			char[] seq = ps[0].toCharArray();
//			insertNodeByOrder(seq, ca, di.getRoot(), 0);
//			// remember to add these nodes to index CAs.
//			di.addNodeValueToCAs(ca);
//		} else {
//			System.err.println("Unknown chars in the index file to read: "
//					+ line);
//			throw new RuntimeException();
//		}
//		return;
		String[] ps = line.split(Pattern.quote(IndexTreeNode.pathsep));
		ConceptAssertion ca = null;
		if (ps.length == 1) {
			ca = (ConceptAssertion) Parser.parseAssertions(
					Parser.getListOfTokensFromTokenizer(ps[0]), null);
		} else if (ps.length == 2) {
			ca = (ConceptAssertion) Parser.parseAssertions(
					Parser.getListOfTokensFromTokenizer(ps[1]), null);
		} else {
			System.err.println("Unknown chars in the index file to read: "
					+ line);
			throw new RuntimeException();
		}
		di.insert(ca);
//		di.addNodeValueToCAs(ca);
		return;
	}

	private static void insertNodeByOrder(char[] seq, ConceptAssertion ca,
			IndexTreeNode father, int idx) {
		boolean last = seq.length == idx + 1;
		if (last) {
			father.getLink()
					.set(seq[idx] == '0' ? 0 : 1, new IndexTreeNode(ca));
			return;
		}
		char sym = seq[idx];
		if (sym == '0') {
			// left
			if (father.getLeft() == null) {
				father.getLink().set(0, new IndexTreeNode(null));
			}
			father = father.getLeft();
			insertNodeByOrder(seq, ca, father, idx + 1);
		} else if (sym == '1') {
			if (father.getRight() == null) {
				father.getLink().set(1, new IndexTreeNode(null));
			}
			father = father.getRight();
			insertNodeByOrder(seq, ca, father, idx + 1);
		} else {
			System.err.println("Unknown char in the leading order : "
					+ seq.toString());
			throw new RuntimeException();
		}

	}

	// private static String readIndex(InputStream is, String encoding){
	// StringBuffer buffer = new StringBuffer();
	// try {
	// InputStreamReader isr = new InputStreamReader(is, encoding);
	// Reader in = new BufferedReader(isr);
	// int ch;
	// while ((ch = in.read()) > -1) {
	// buffer.append((char)ch);
	// }
	// in.close();
	// return buffer.toString();
	// } catch (IOException e) {
	// e.printStackTrace();
	// return null;
	// }
	//
	// }

}
